package Algorithms;

import NeuronNetworkLibrary.Network;
import java.util.Random;

/**
 * Implementation of Simulated Annealing algorithm for Neural Network.
 *
 * @author Zbyszko
 */
public class NeuralSimulatedAnnealing {

    Network network;
    private long cycles;
    private double startingTemp;
    private double stopTemperature;
    private double temperature;
    private double coolingRatio;

    /**
     * Inits the neural simulated annealing object responsible for calculating
     * the algorithm.
     *
     * @param network neural neutwork.
     * @param cycles number of cycles (iterations).
     * @param startingTemp starting temperature value.
     * @param stopTemp cooling rate value.
     */
    public NeuralSimulatedAnnealing(Network network, long cycles, double startingTemp, double stopTemp) {
        this.network = network;
        this.cycles = cycles;
        this.startingTemp = startingTemp;
        this.temperature = startingTemp;
        this.stopTemperature = stopTemp;
        this.coolingRatio = 0.99;//Math.exp(Math.log(this.stopTemperature / this.startingTemp) / (cycles - 1));
    }

    /**
     * Calculates the algorithm.
     *
     * @param currentPatternNumber Current pattern number.
     */
    public void annealNetwork() {
        
        Random rand = new Random();
        double propability,value;
        
        double currentMSE;     
        this.feedforward();
        double globalMSE = network.calculateRMSE(); //network.getMSE();
        //System.out.println("globalMSE=" + globalMSE);      
        for (int i = 0; i < this.cycles; i++) {
            
            changeWeights();
            this.feedforward();
            currentMSE = network.calculateRMSE(); //network.getMSE();
            //System.out.println("currentMSE=" + currentMSE);
            if (globalMSE > currentMSE) {
                //System.out.println("bbb");
                globalMSE = network.calculateRMSE(); //network.getMSE();
            } else {
                //System.out.println("aaa");
                propability = Math.exp(-(currentMSE-globalMSE) / this.temperature);
                value = rand.nextDouble();
                System.out.println("prop=" + propability + " value=" + value);
                if(value <= propability) {
                    this.revertWeights();
                    this.feedforward();
                }
            } 
                      
        }this.temperature *= coolingRatio;
        
    }

    private void feedforward(){
        for (int i = 0; i < network.getNumberOfPatterns(); i++) {
            network.calculateNetwork(i);
        }
    }
    
    
    private void changeWeights() {

        for (int i = 0; i < network.getHiddenLayers().size(); i++) {
            for (int j = 0; j < network.getHiddenLayers().get(i).size(); j++) {

                double w[] = network.getHiddenLayers().get(i).get(j).getInputWeights();
                network.getHiddenLayers().get(i).get(j).setPreviousWeights(w.clone());                
                network.getHiddenLayers().get(i).get(j).setInputWeights(
                        this.changeWeightsArray(w));
            }
        }

        for (int i = 0; i < network.getOutputLayer().size(); i++) {

            double[]w = network.getOutputLayer().get(i).getInputWeights();
            network.getOutputLayer().get(i).setPreviousWeights(w.clone());
            network.getOutputLayer().get(i).setInputWeights(
                    this.changeWeightsArray(w));
        }       
    }

    private double[] changeWeightsArray(double[] weights) {
        double add = 0;
        for (int k = 0; k < weights.length; k++) {
            add = Math.random() * 2;
            add = add - 1;
            add = add/200;
            weights[k] = weights[k] + add;
        }
        return weights;
    }
    
    private void revertWeights(){
        
        for (int i = 0; i < network.getHiddenLayers().size(); i++) {
            for (int j = 0; j < network.getHiddenLayers().get(i).size(); j++) {
                double[] weights = network.getHiddenLayers().get(i).get(j).getPreviousWeights();                
                network.getHiddenLayers().get(i).get(j).setInputWeights(weights);
            }
        }

        for (int i = 0; i < network.getOutputLayer().size(); i++) {
            double[] weights = network.getOutputLayer().get(i).getPreviousWeights();
            network.getOutputLayer().get(i).setInputWeights(weights);
        }        
    }    
}